use actix_files as afs;
use actix_web::{middleware, web, App, HttpServer,dev::ServiceRequest, Error};
use diesel::r2d2::ConnectionManager;
use diesel::MysqlConnection;

use tera::Tera;
use tutorial::{
    controller::{admin, front},
    utils,
};

use actix_web_httpauth::extractors::basic::{BasicAuth, Config};
use actix_web_httpauth::extractors::AuthenticationError;
use actix_web_httpauth::middleware::HttpAuthentication;
type DbPool = r2d2::Pool<ConnectionManager<MysqlConnection>>;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    std::env::set_var("RUST_LOG", format!("actix_web={}",std::env::var("DATABASE_URL").unwrap_or("info".into())));
    env_logger::init();
    dotenv::dotenv().ok();

    let pool = establish_connection();

    HttpServer::new(move || {
        let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();

        let auth = HttpAuthentication::basic(basic_auth_validator);

        let app = App::new();
        app.data(tera)
            .data(pool.clone())
            .wrap(middleware::Logger::default()) // enable logger
            // admin
            .service(
                web::scope("/admin/")
                    .wrap(auth)
                    .service(admin::index::index)
                    .service(admin::types::list)
                    .service(admin::types::type_add)
                    .service(admin::types::type_edit)
                    .service(admin::types::delete)
                    .service(admin::article::list)
                    .service(admin::article::add_page)
                    .service(admin::article::add)
                    .service(admin::article::delete)
                    .service(admin::article::edit)
                    .service(admin::article::upload)
                    .service(admin::article::get_all_by_tid)
                    .service(admin::article::edit_page)
            )
            // front
            .service(front::index::index)
            .service(front::index::article)
            .service(front::index::goto_first_tutorial)
            // file
            .service(afs::Files::new("/", "./static/").show_files_listing())
            .service(web::scope("").wrap(utils::errors::error_handlers()))
    })
    .bind(std::env::var("HOST_PORT").unwrap_or("127.0.0.1:80".into()))?
    .run()
    .await
}

fn establish_connection() -> DbPool {
    let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL");
    let manager = ConnectionManager::<MysqlConnection>::new(db_url);
    r2d2::Pool::builder()
        .build(manager)
        .expect("Failed to create pool.")
}

async fn basic_auth_validator(
    req: ServiceRequest,
    credentials: BasicAuth,
) -> Result<ServiceRequest, Error> {
    let config = req
        .app_data::<Config>()
        .map(|data| data.clone())
        .unwrap_or_else(Default::default);
    match validate_credentials(
        credentials.user_id(),
        credentials.password().unwrap().trim(),
    ) {
        Ok(res) => {
            if res == true {
                Ok(req)
            } else {
                Err(AuthenticationError::from(config).into())
            }
        }
        Err(_) => Err(AuthenticationError::from(config).into()),
    }
}

fn validate_credentials(user_id: &str, user_password: &str) -> Result<bool, std::io::Error> {
    let name = std::env::var("ADMIN").expect("ADMIN");
    let pass = std::env::var("PASSWORD").expect("PASSWORD");

    println!("name:{}, pass:{}", name, pass);

    if user_id.eq(&name) && user_password.eq(&pass) {
        return Ok(true);
    }
    return Err(std::io::Error::new(
        std::io::ErrorKind::Other,
        "Authentication failed!",
    ));
}
